package com.vteba.security.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

/**
 * Spring Security用户登录的过滤器。
 * @author yinlei
 * @date 2014年11月4日 上午10:49:33
 */
public class DefaultUserAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
	public static final String SECURITY_FORM_USERNAME = "username";
	public static final String SECURITY_FORM_PASSWORD = "password";
	public static final String SECURITY_FORM_ORG_CODE = "orgCode";
	public static final String SECURITY_FORM_AUTHCODE = "authCode";
	
	private static final String AUTH_ERROR = "Authentication authCode error.";
	private static final String PARAM_ERROR = "Authentication param's cannot be null.";
	
	private String usernameParameter = SECURITY_FORM_USERNAME;
	private String passwordParameter = SECURITY_FORM_PASSWORD;
	private String orgCodeParameter = SECURITY_FORM_ORG_CODE;
	private String authCodeParameter = SECURITY_FORM_AUTHCODE;
	private boolean postOnly = true;
	
	private boolean hasAuthCode = true;
	
	// 区分大小写，默认不区分
	private boolean caseSensitive;

	// ~ Constructors
	// ===================================================================================================

	public DefaultUserAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}

	// ~ Methods
	// ========================================================================================================

	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: "
					+ request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);
		String orgCode = obtainOrgCode(request);
		String authCode = null;
		
		if (hasAuthCode) {
			authCode = obtainAuthCode(request);
			if (authCode != null) {
				// 或者拿到ajax中验证
				String random = (String) request.getSession().getAttribute("random");
				if (!authCode.equalsIgnoreCase(random)) {
					failure(request, response, AUTH_ERROR);
					return null;
				}
			} else {
				failure(request, response, AUTH_ERROR);
				return null;
			}
		}
		
		if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
			failure(request, response, PARAM_ERROR);
			return null;
		}

		if (!isCaseSensitive()) { // 区分大小写，默认不区分
			username = username.trim();
		}

		DefaultUserAuthenticationToken authRequest = new DefaultUserAuthenticationToken(
				username, password, orgCode, authCode);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		Authentication authentication = getAuthenticationManager().authenticate(authRequest);
		
		return authentication;
	}

	private void failure(HttpServletRequest request, HttpServletResponse response, String message) {
		AuthenticationException failed = new AuthenticationServiceException(message);
		try {
			unsuccessfulAuthentication(request, response, failed);
		} catch (Exception e) {
			
		}
	}

	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	protected String obtainOrgCode(HttpServletRequest request) {
		return request.getParameter(orgCodeParameter);
	}
	
	protected String obtainAuthCode(HttpServletRequest request) {
		return request.getParameter(authCodeParameter);
	}
	
	/**
	 * Provided so that subclasses may configure what is put into the
	 * authentication request's details property.
	 *
	 * @param request
	 *            that an authentication request is being created for
	 * @param authRequest
	 *            the authentication request object that should have its details
	 *            set
	 */
	protected void setDetails(HttpServletRequest request,
			DefaultUserAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * Sets the parameter name which will be used to obtain the username from
	 * the login request.
	 *
	 * @param usernameParameter
	 *            the parameter name. Defaults to "username".
	 */
	public void setUsernameParameter(String usernameParameter) {
		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
		this.usernameParameter = usernameParameter;
	}

	/**
	 * Sets the parameter name which will be used to obtain the password from
	 * the login request..
	 *
	 * @param passwordParameter
	 *            the parameter name. Defaults to "password".
	 */
	public void setPasswordParameter(String passwordParameter) {
		Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
		this.passwordParameter = passwordParameter;
	}

	/**
	 * Defines whether only HTTP POST requests will be allowed by this filter.
	 * If set to true, and an authentication request is received which is not a
	 * POST request, an exception will be raised immediately and authentication
	 * will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
	 * will be called as if handling a failed authentication.
	 * <p>
	 * Defaults to <tt>true</tt> but may be overridden by subclasses.
	 */
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}

	public final String getUsernameParameter() {
		return usernameParameter;
	}

	public final String getPasswordParameter() {
		return passwordParameter;
	}

	public String getOrgCodeParameter() {
		return orgCodeParameter;
	}

	public void setOrgCodeParameter(String orgCodeParameter) {
		this.orgCodeParameter = orgCodeParameter;
	}

	public String getAuthCodeParameter() {
		return authCodeParameter;
	}

	public void setAuthCodeParameter(String authCodeParameter) {
		this.authCodeParameter = authCodeParameter;
	}

	public boolean isCaseSensitive() {
		return caseSensitive;
	}

	public void setCaseSensitive(boolean caseSensitive) {
		this.caseSensitive = caseSensitive;
	}

	public boolean isHasAuthCode() {
		return hasAuthCode;
	}

	public void setHasAuthCode(boolean hasAuthCode) {
		this.hasAuthCode = hasAuthCode;
	}
}
